<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_18</span></h2>

  <p>quot; + s);</p>

  <p>Snake s2 = (Snake)s.clone();</p>

  <p>System.out.println("s2 = " + s2);</p>

  <p>s.increment();</p>

  <p>System.out.println(</p>

  <p>"after s.increment, s2 = " + s2);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>一条Snake（蛇）由数段构成，每一段的类型都是Snake。所以，这是一个一段段链接起来的列表。所有段都是以循环方式创建的，每做好一段，都会使第一个构建器参数的值递减，直至最终为零。而为给每段赋予一个独一无二的标记，第二个参数（一个Char）的值在每次循环构建器调用时都会递增。</p>

  <p>increment()方法的作用是循环递增每个标记，使我们能看到发生的变化；而toString则循环打印出每个标记。输出如下：</p>

  <p>s = :a:b:c:d:e</p>

  <p>s2 = :a:b:c:d:e</p>

  <p>after s.increment, s2 = :a:c:d:e:f</p>

  <p>这意味着只有第一段才是由Object.clone()复制的，所以此时进行的是一种“浅层复制”。若希望复制整条蛇――即进行“深层复制”――必须在被覆盖的clone()里采取附加的操作。</p>

  <p>通常可在从一个能克隆的类里调用super.clone()，以确保所有基础类行动（包括Object.clone()）能够进行。随着是为对象内每个句柄都明确调用一个clone()；否则那些句柄会别名变成原始对象的句柄。构建器的调用也大致相同――首先构造基础类，然后是下一个衍生的构建器……以此类推，直到位于最深层的衍生构建器。区别在于clone()并不是个构建器，所以没有办法实现自动克隆。为了克隆，必须由自己明确进行。</p>

  <p>12.2.6 克隆合成对象</p>

  <p>试图深层复制合成对象时会遇到一个问题。必须假定成员对象中的clone()方法也能依次对自己的句柄进行深层复制，以此类推。这使我们的操作变得复杂。为了能正常实现深层复制，必须对所有类中的代码进行控制，或者至少全面掌握深层复制中需要涉及的类，确保它们自己的深层复制能正确进行。</p>

  <p>下面这个例子总结了面对一个合成对象进行深层复制时需要做哪些事情：</p>

  <p>//: DeepCopy.java</p>

  <p>// Cloning a composed object</p>

  <p>class DepthReading implements Cloneable {</p>

  <p>private double depth;</p>

  <p>public DepthReading(double depth) {</p>

  <p>this.depth = depth;</p>

  <p>}</p>

  <p>public Object clone() {</p>

  <p>Object o = null;</p>

  <p>try {</p>

  <p>o = super.clone();</p>

  <p>} catch (CloneNotSupportedException e) {</p>

  <p>e.printStackTrace();</p>

  <p>}</p>

  <p>return o;</p>

  <p>}</p>

  <p>}</p>

  <p>class TemperatureReading implements Cloneable {</p>

  <p>private long time;</p>

  <p>private double temperature;</p>

  <p>public TemperatureReading(double temperature) {</p>

  <p>time = System.currentTimeMillis();</p>

  <p>this.temperature = temperature;</p>

  <p>}</p>

  <p>public Object clone() {</p>

  <p>Object o = null;</p>

  <p>try {</p>

  <p>o = super.clone();</p>

  <p>} catch (CloneNotSupportedException e) {</p>

  <p>e.printStackTrace();</p>

  <p>}</p>

  <p>return o;</p>

  <p>}</p>

  <p>}</p>

  <p>class OceanReading implements Cloneable {</p>

  <p>private DepthReading depth;</p>

  <p>private TemperatureReading temperature;</p>

  <p>public OceanReading(double tdata, double ddata){</p>

  <p>temperature = new TemperatureReading(tdata);</p>

  <p>depth = new DepthReading(ddata);</p>

  <p>}</p>

  <p>public Object clone() {</p>

  <p>OceanReading o = null;</p>

  <p>try {</p>

  <p>o = (OceanReading)super.clone();</p>

  <p>} catch (CloneNotSupportedException e) {</p>

  <p>e.printStackTrace();</p>

  <p>}</p>

  <p>// Must clone handles:</p>

  <p>o.depth = (DepthReading)o.depth.clone();</p>

  <p>o.temperature =</p>

  <p>(TemperatureReading)o.temperature.clone();</p>

  <p>return o; // Upcasts back to Object</p>

  <p>}</p>

  <p>}</p>

  <p>public class DeepCopy {</p>

  <p>public static void main(String[] args) {</p>

  <p>OceanReading reading =</p>

  <p>new OceanReading(33.9, 100.5);</p>

  <p>// Now clone it:</p>

  <p>OceanReading r =</p>

  <p>(OceanReading)reading.clone();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>DepthReading和TemperatureReading非常相似；它们都只包含了基本数据类型。所以clone()方法能够非常简单：调用super.clone()并返回结果即可。注意两个类使用的clone()代码是完全一致的。</p>

  <p>OceanReading是由DepthReading和TemperatureReading对象合并而成的。为了对其进行深层复制，clone()必须同时克隆OceanReading内的句柄。为达到这个目标，super.clone()的结果必须造型成一个OceanReading对象（以便访问depth和temperature句柄）。</p>

  <p>12.2.7 用Vector进行深层复制</p>

  <p>下面让我们复习一下本章早些时候提出的Vector例子。这一次Int2类是可以克隆的，所以能对Vector进行深层复制：</p>

  <p>//: AddingClone.java</p>

  <p>// You must go through a few gyrations to</p>

  <p>// add cloning to your own class.</p>

  <p>import java.util.*;</p>

  <p>class Int2 implements Cloneable {</p>

  <p>private int i;</p>

  <p>public Int2(int ii) { i = ii; }</p>

  <p>public void increment() { i++; }</p>

  <p>public String toString() {</p>

  <p>return Integer.toString(i);</p>

  <p>}</p>

  <p>public Object clone() {</p>

  <p>Object o = null;</p>

  <p>try {</p>

  <p>o = super.clone();</p>

  <p>} catch (CloneNotSupportedException e) {</p>

  <p>System.out.println("Int2 can't clone");</p>

  <p>}</p>

  <p>return o;</p>

  <p>}</p>

  <p>}</p>

  <p>// Once it's cloneable, inheritance</p>

  <p>// doesn't remove cloneability:</p>

  <p>class Int3 extends Int2 {</p>

  <p>private int j; // Automatically duplicated</p>

  <p>public Int3(int i) { super(i); }</p>

  <p>}</p>

  <p>public class AddingClone {</p>

  <p>public static void main(String[] args) {</p>

  <p>Int2 x = new Int2(10);</p>

  <p>Int2 x2 = (Int2)x.clone();</p>

  <p>x2.increment();</p>

  <p>System.out.println(</p>

  <p>"x = " + x + ", x2 = " + x2);</p>

  <p>// Anything inherited is also cloneable:</p>

  <p>Int3 x3 = new Int3(7);</p>

  <p>x3 = (Int3)x3.clone();</p>

  <p>Vector v = new Vector();</p>

  <p>for(int i = 0; i &lt; 10; i++ )</p>

  <p>v.addElement(new Int2(i));</p>

  <p>System.out.println("v: " + v);</p>

  <p>Vector v2 = (Vector)v.clone();</p>

  <p>// Now clone each element:</p>

  <p>for(int i = 0; i &lt; v.size(); i++)</p>

  <p>v2.setElementAt(</p>

  <p>((Int2)v2.elementAt(i)).clone(), i);</p>

  <p>// Increment all v2's elements:</p>

  <p>for(Enumeration e = v2.elements();</p>

  <p>e.hasMoreElements(); )</p>

  <p>((Int2)e.nextElement()).increment();</p>

  <p>// See if it changed v's elements:</p>

  <p>System.out.println("v: " + v);</p>

  <p>System.out.println("v2: " + v2);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>Int3自Int2继承而来，并添加了一个新的基本类型成员int j。大家也许认为自己需要再次覆盖clone()，以确保j得到复制，但实情并非如此。将Int2的clone()当作Int3的clone()调用时，它会调用Object.clone()，判断出当前操作的是Int3，并复制Int3内的所有二进制位。只要没有新增需要克隆的句柄，对Object.clone()的一个调用就能完成所有必要的复制――无论clone()是在层次结构多深的一级定义的。</p>

  <p>至此，大家可以总结出对Vector进行深层复制的先决条件：在克隆了Vector后，必须在其中遍历，并克隆由Vector指向的每个对象。为了对Hashtable（散列表）进行深层复制，也必须采取类似的处理。</p>

  <p>这个例子剩余的部分显示出克隆已实际进行――证据就是在克隆了对象以后，可以自由改变它，而原来那个对象不受任何影响。</p>

  <p>12.2.8 通过序列化进行深层复制</p>

  <p>若研究一下第10章介绍的那个Java 1.1对象序列化示例，可能发现若在一个对象序列化以后再撤消对它的序列化，或者说进行装配，那么实际经历的正是一个“克隆”的过程。</p>

  <p>那么为什么不用序列化进行深层复制呢？下面这个例子通过计算执行时间对比了这两种方法：</p>

  <p>//: Compete.java</p>

  <p>import java.io.*;</p>

  <p>class Thing1 implements Serializable {}</p>

  <p>class Thing2 implements Serializable {</p>

  <p>Thing1 o1 = new Thing1();</p>

  <p>}</p>

  <p>class Thing3 implements Cloneable {</p>

  <p>public Object clone() {</p>

  <p>Object o = null;</p>

  <p>try {</p>

  <p>o = super.clone();</p>

  <p>} catch (CloneNotSupportedException e) {</p>

  <p>System.out.println("Thing3 can't clone");</p>

  <p>}</p>

  <p>return o;</p>

  <p>}</p>

  <p>}</p>

  <p>class Thing4 implements Cloneable {</p>

  <p>Thing3 o3 = new Thing3();</p>

  <p>public Object clone() {</p>

  <p>Thing4 o = null;</p>

  <p>try {</p>

  <p>o = (Thing4)super.clone();</p>

  <p>} catch (CloneNotSupportedException e) {</p>

  <p>System.out.println("Thing4 can't clone");</p>

  <p>}</p>

  <p>// Clone the field, too:</p>

  <p>o.o3 = (Thing3)o3.clone();</p>

  <p>return o;</p>

  <p>}</p>

  <p>}</p>

  <p>public class Compete {</p>

  <p>static final int SIZE = 5000;</p>

  <p>public static void main(String[] args) {</p>

  <p>Thing2[] a = new Thing2[SIZE];</p>

  <p>for(int i = 0; i &lt; a.length; i++)</p>

  <p>a[i] = new Thing2();</p>

  <p>Thing4[] b = new Thing4[SIZE];</p>

  <p>for(int i = 0; i &lt; b.length; i++)</p>

  <p>b[i] = new Thing4();</p>

  <p>try {</p>

  <p>long t1 = System.currentTimeMillis();</p>

  <p>ByteArrayOutputStream buf =</p>

  <p>new ByteArrayOutputStream();</p>

  <p>ObjectOutputStream o =</p>

  <p>new ObjectOutputStream(buf);</p>

  <p>for(int i = 0; i &lt; a.length; i++)</p>

  <p>o.writeObject(a[i]);</p>

  <p>// Now get copies:</p>

  <p>ObjectInputStream in =</p>

  <p>new ObjectInputStream(</p>

  <p>new ByteArrayInputStream(</p>

  <p>buf.toByteArray()));</p>

  <p>Thing2[] c = new Thing2[SIZE];</p>

  <p>for(int i = 0; i &lt; c.length; i++)</p>

  <p>c[i] = (Thing2)in.readObject();</p>

  <p>long t2 = System.currentTimeMillis();</p>

  <p>System.out.println(</p>

  <p>"Duplication via serialization: " +</p>

  <p>(t2 - t1) + " Milliseconds");</p>

  <p>// Now try cloning:</p>

  <p>t1 = System.currentTimeMillis();</p>

  <p>Thing4[] d = new Thing4[SIZE];</p>

  <p>for(int i = 0; i &lt; d.length; i++)</p>

  <p>d[i] = (Thing4)b[i].clone();</p>

  <p>t2 = System.currentTimeMillis();</p>

  <p>System.out.println(</p>

  <p>"Duplication via cloning: " +</p>

  <p>(t2 - t1) + " Milliseconds");</p>

  <p>} catch(Exception e) {</p>

  <p>e.printStackTrace();</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>其中，Thing2和Thing4包含了成员对象，所以需要进行一些深层复制。一个有趣的地方是尽管Serializable类很容易设置，但在复制它们时却要做多得多的工作。克隆涉及到大量的类设置工作，但实际的对象复制是相当简单的。结果很好地说明了一切。下面是几次运行分别得到的结果：</p>

  <p>的确</p>

  <p>Duplication via serialization: 3400 Milliseconds</p>

  <p>Duplication via cloning: 110 Milliseconds</p>

  <p>Duplication via serialization: 3410 Milliseconds</p>

  <p>Duplication via cloning: 110 Milliseconds</p>

  <p>Duplication via serialization: 3520 Milliseconds</p>

  <p>Duplication via cloning: 110 Milliseconds</p>

  <p>除了序列化和克隆之间巨大的时间差异以外，我们也注意到序列化技术的运行结果并不稳定，而克隆每一次花费的时间都是相同的。</p>

  <p>12.2.9 使克隆具有更大的深度</p>

  <p>若新建一个类，它的基础类会默认为Object，并默认为不具备克隆能力（就象在下一节会看到的那样）。只要不明确地添加克隆能力，这种能力便不会自动产生。但我们可以在任何层添加它，然后便可从那个层开始向下具有克隆能力。如下所示：</p>

  <p>//: HorrorFlick.java</p>

  <p>// You can insert Cloneability at any</p>

  <p>// level of inheritance.</p>

  <p>import java.util.*;</p>

  <p>class Person {}</p>

  <p>class Hero extends Person {}</p>

  <p>class Scientist extends Person</p>

  <p>implements Cloneable {</p>

  <p>public Object clone() {</p>

  <p>try {</p>

  <p>return super.clone();</p>

  <p>} catch (CloneNotSupportedException e) {</p>

  <p>// this should never happen:</p>

  <p>// It's Cloneable already!</p>

  <p>throw new InternalError();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class MadScientist extends Scientist {}</p>

  <p>public class HorrorFlick {</p>

  <p>public static void main(String[] args) {</p>

  <p>Person p = new Person();</p>

  <p>Hero h = new Hero();</p>

  <p>Scientist s = new Scientist();</p>

  <p>MadScientist m = new MadScientist();</p>

  <p>// p = (Person)p.clone(); // Compile error</p>

  <p>// h = (Hero)h.clone(); // Compile error</p>

  <p>s = (Scientist)s.clone();</p>

  <p>m = (MadScientist)m.clone();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>添加克隆能力之前，编译器会阻止我们的克隆尝试。一旦在Scientist里添加了克隆能力，那么Scientist以及它的所有“后裔”都可以克隆。</p>

  <p>12.2.10 为什么有这个奇怪的设计</p>

  <p>之所以感觉这个方案的奇特，因为它事实上的确如此。也许大家会奇怪它为什么要象这样运行，而该方案背后的真正含义是什么呢？后面讲述的是一个未获证实的故事――大概是由于围绕Java的许多买卖使其成为一种设计优良的语言――但确实要花许多口舌才能讲清楚这背后发生的所有事情。</p>

  <p>最初，Java只是作为一种用于控制硬件的语言而设计，与因特网并没有丝毫联系。象这样一类面向大众的语言一样，其意义在于程序员可以对任意一个对象进行克隆。这样一来，clone()就放置在根类Object里面，但因为它是一种公用方式，因而我们通常能够对任意一个对象进行克隆。看来这是最灵活的方式了，毕竟它不会带来任何害处。</p>

  <p>正当Java看起来象一种终级因特网程序设计语言的时候，情况却发生了变化。突然地，人们提出了安全问题，而且理所当然，这些问题与使用对象有关，我们不愿望任何人克隆自己的保密对象。所以我们最后看到的是为原来那个简单、直观的方案添加的大量补丁：clone()在Object里被设置成“protected”。必须将其覆盖，并使用“implement Cloneable”，同时解决违例的问题。</p>

  <p>只有在准备调用Object的clone()方法时，才没有必要使用Cloneable接口，因为那个方法会在运行期间得到检查，以确保我们的类实现了Cloneable。但为了保持连贯性（而且由于Cloneable无论如何都是空的），最好还是由自己实现Cloneable。</p>

  <p>12.3 克隆的控制</p>

  <p>为消除克隆能力，大家也许认为只需将clone()方法简单地设为private（私有）即可，但这样是行不通的，因为不能采用一个基础类方法，并使其在衍生类中更“私有”。所以事情并没有这么简单。此外，我们有必要控制一个对象是否能够克隆。对于我们设计的一个类，实际有许多种方案都是可以采取的：</p>

  <p>(1) 保持中立，不为克隆做任何事情。也就是说，尽管不可对我们的类克隆，但从它继承的一个类却可根据实际情况决定克隆。只有Object.clone()要对类中的字段进行某些合理的操作时，才可以作这方面的决定。</p>

  <p>(2) 支持clone()，采用实现Cloneable（可克隆）能力的标准操作，并覆盖clone()。在被覆盖的clone()中，可调用super.clone()，并捕获所有违例（这样可使clone()不“掷”出任何违例）。</p>

  <p>(3) 有条件地支持克隆。若类容纳了其他对象的句柄，而那些对象也许能够克隆（集合类便是这样的一个例子），就可试着克隆拥有对方句柄的所有对象；如果它们“掷”出了违例，只需让这些违例通过即可。举个例子来说，假设有一个特殊的Vector，它试图克隆自己容纳的所有对象。编写这样的一个Vector时，并不知道客户程序员会把什么形式的对象置入这个Vector中，所以并不知道它们是否真的能够克隆。</p>

  <p>(4) 不实现Cloneable()，但是将clone()覆盖成protected，使任何字段都具有正确的复制行为。这样一来，从这个类继承的所有东西都能覆盖clone()，并调用super.clone()来产生正确的复制行为。注意在我们实现方案里，可以而且应该调用super.clone()――即使那个方法本来预期的是一个Cloneable对象（否则会掷出一个违例），因为没有人会在我们这种类型的对象上直接调用它。它只有通过一个衍生类调用；对那个衍生类来说，如果要保证它正常工作，需实现Cloneable。</p>

  <p>(5) 不实现Cloneable来试着防止克隆，并覆盖clone()，以产生一个违例。为使这一设想顺利实现，只有令从它衍生出来的任何类都调用重新定义后的clone()里的suepr.clone()。</p>

  <p>(6) 将类设为final，从而防止克隆。若clone()尚未被我们的任何一个上级类覆盖，这一设想便不会成功。若已被覆盖，那么再一次覆盖它，并“掷”出一个CloneNotSupportedException（克隆不支持）违例。为担保克隆被禁止，将类设为final是唯一的办法。除此以外，一旦涉及保密对象或者遇到想对创建的对象数量进行控制的其他情况，应该将所有构建器都设为private，并提供一个或更多的特殊方法来创建对象。采用这种方式，这些方法就可以限制创建的对象数量以及它们的创建条件――一种特殊情况是第16章要介绍的singleton（独子）方案。</p>

  <p>下面这个例子总结了克隆的各种实现方法，然后在层次结构中将其“关闭”：</p>

  <p>//: CheckCloneable.java</p>

  <p>// Checking to see if a handle can be cloned</p>

  <p>// Can't clone this because it doesn't</p>

  <p>// override clone():</p>

  <p>class Ordinary {}</p>

  <p>// Overrides clone, but doesn't implement</p>

  <p>// Cloneable:</p>

  <p>class WrongClone extends Ordinary {</p>

  <p>public Object clone()</p>

  <p>throws CloneNotSupportedException {</p>

  <p>return super.clone(); // Throws exception</p>

  <p>}</p>

  <p>}</p>

  <p>// Does all the right things for cloning:</p>

  <p>class IsCloneable extends Ordinary</p>

  <p>implements Cloneable {</p>

  <p>public Object clone()</p>

  <p>throws CloneNotSupportedException {</p>

  <p>return super.clone();</p>

  <p>}</p>

  <p>}</p>

  <p>// Turn off cloning by throwing the exception:</p>

  <p>class NoMore extends IsCloneable {</p>

  <p>public Object clone()</p>

  <p>throws CloneNotSupportedException {</p>

  <p>throw new CloneNotSupportedException();</p>

  <p>}</p>

  <p>}</p>

  <p>class TryMore extends NoMore {</p>

  <p>public Object clone()</p>

  <p>throws CloneNotSupportedException {</p>

  <p>// Calls NoMore.clone(), throws exception:</p>

  <p>return super.clone();</p>

  <p>}</p>

  <p>}</p>

  <p>class BackOn extends NoMore {</p>

  <p>private BackOn duplicate(BackOn b) {</p>

  <p>// Somehow make a copy of b</p>

  <p>// and return that copy. This is a dummy</p>

  <p>// copy, just to make the point:</p>

  <p>return new BackOn();</p>

  <p>}</p>

  <p>public Object clone() {</p>

  <p>// Doesn't call NoMore.clone():</p>

  <p>return duplicate(this);</p>

  <p>}</p>

  <p>}</p>

  <p>// Can't inherit from this, so can't override</p>

  <p>// the clone method like in BackOn:</p>

  <p>final class ReallyNoMore extends NoMore {}</p>

  <p>public class CheckCloneable {</p>

  <p>static Ordinary tryToClone(Ordinary ord) {</p>

  <p>String id = ord.getClass().getName();</p>

  <p>Ordinary x = null;</p>

  <p>if(ord instanceof Cloneable) {</p>

  <p>try {</p>

  <p>System.out.println("Attempting " + id);</p>

  <p>x = (Ordinary)((IsCloneable)ord).clone();</p>

  <p>System.out.println("Cloned " + id);</p>

  <p>} catch(CloneNotSupportedException e) {</p>

  <p>System.out.println(</p>

  <p>"Could not clone " + id);</p>

  <p>}</p>

  <p>}</p>

  <p>return x;</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>// Upcasting:</p>

  <p>Ordinary[] ord = {</p>

  <p>new IsCloneable(),</p>

  <p>new WrongClone(),</p>

  <p>new NoMore(),</p>

  <p>new TryMore(),</p>

  <p>new BackOn(),</p>

  <p>new ReallyNoMore(),</p>

  <p>};</p>

  <p>Ordinary x = new Ordinary();</p>

  <p>// This won't compile, since clone() is</p>

  <p>// protected in Object:</p>

  <p>//! x = (Ordinary)x.clone();</p>

  <p>// tryToClone() checks first to see if</p>

  <p>// a class implements Cloneable:</p>

  <p>for(int i = 0; i &lt; ord.length; i++)</p>

  <p>tryToClone(ord[i]);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>第一个类Ordinary代表着大家在本书各处最常见到的类：不支持克隆，但在它正式应用以后，却也不禁止对其克隆。但假如有一个指向Ordinary对象的句柄，而且那个对象可能是从一个更深的衍生类上溯造型来的，便不能判断它到底能不能克隆。</p>

  <p>WrongClone类揭示了实现克隆的一种不正确途径。它确实覆盖了Object.clone()，并将那个方法设为public，但却没有实现Cloneable。所以一旦发出对super.clone()的调用（由于对Object.clone()的一个调用造成的），便会无情地掷出CloneNotSupportedException违例。</p>

  <p>在IsCloneable中，大家看到的才是进行克隆的各种正确行动：先覆盖clone()，并实现了Cloneable。但是，这个clone()方法以及本例的另外几个方法并不捕获CloneNotSupportedException违例，而是任由它通过，并传递给调用者。随后，调用者必须用一个try-catch代码块把它包围起来。在我们自己的clone()方法中，通常需要在clone()内部捕获CloneNotSupportedException违例，而不是任由它通过。正如大家以后会理解的那样，对这个例子来说，让它通过是最正确的做法。</p>

  <p>类NoMore试图按照Java设计者打算的那样“关闭”克隆：在衍生类clone()中，我们掷出CloneNotSupportedException违例。TryMore类中的clone()方法正确地调用super.clone()，并解析成NoMore.clone()，后者掷出一个违例并禁止克隆。</p>

  <p>但在已被覆盖的clone()方法中，假若程序员不遵守调用super.clone()的“正确”方法，又会出现什么情况呢？在BackOn中，大家可看到实际会发生什么。这个类用一个独立的方法duplicate()制作当前对象的一个副本，并在clone()内部调用这个方法，而不是调用super.clone()。违例永远不会产生，而且新类是可以克隆的。因此，我们不能依赖“掷”出一个违例的方法来防止产生一个可克隆的类。唯一安全的方法在ReallyNoMore中得到了演示，它设为final，所以不可继承。这意味着假如clone()在final类中掷出了一个违例，便不能通过继承来进行修改，并可有效地禁止克隆（不能从一个拥有任意继承级数的类中明确调用Object.clone()；只能调用super.clone()，它只可访问直接基础类）。因此，只要制作一些涉及安全问题的对象，就最好把那些类设为final。</p>

  <p>在类CheckCloneable中，我们看到的第一个类是tryToClone()，它能接纳任何Ordinary对象，并用instanceof检查它是否能够克隆。若答案是肯定的，就将对象造型成为一个IsCloneable，调用clone()，并将结果造型回Ordinary，最后捕获有可能产生的任何违例。请注意用运行期类型鉴定（见第11章）打印出类名，使自己看到发生的一切情况。</p>

  <p>在main()中，我们创建了不同类型的Ordinary对象，并在数组定义中上溯造型成为Ordinary。在这之后的头两行代码创建了一个纯粹的Ordinary对象，并试图对其克隆。然而，这些代码不会得到编译，因为clone()是Object中的一个protected（受到保护的）方法。代码剩余的部分将遍历数组，并试着克隆每个对象，分别报告它们的成功或失败。输出如下：</p>

  <p>Attempting IsCloneable</p>

  <p>Cloned IsCloneable</p>

  <p>Attempting NoMore</p>

  <p>Could not clone NoMore</p>

  <p>Attempting TryMore</p>

  <p>Could not clone TryMore</p>

  <p>Attempting BackOn</p>

  <p>Cloned BackOn</p>

  <p>Attempting ReallyNoMore</p>

  <p>Could not clone ReallyNoMore</p>

  <p>总之，如果希望一个类能够克隆，那么：</p>

  <p>(1) 实现Cloneable接口</p>

  <p>(2) 覆盖clone()</p>

  <p>(3) 在自己的clone()中调用super.clone()</p>

  <p>(4) 在自己的clone()中捕获违例</p>

  <p>这一系列步骤能达到最理想的效果。</p>

  <p>12.3.1 副本构建器</p>

  <p>克隆看起来要求进行非常复杂的设置，似乎还该有另一种替代方案。一个办法是制作特殊的构建器，令其负责复制一个对象。在C++中，这叫作“副本构建器”。刚开始的时候，这好象是一种非常显然的解决方案（如果你是C++程序员，这个方法就更显亲切）。下面是一个实际的例子：</p>

  <p>//: CopyConstructor.java</p>

  <p>// A constructor for copying an object</p>

  <p>// of the same type, as an attempt to create</p>

  <p>// a local copy.</p>

  <p>class FruitQualities {</p>

  <p>private int weight;</p>

  <p>private int color;</p>

  <p>private int firmness;</p>

  <p>private int ripeness;</p>

  <p>private int smell;</p>

  <p>// etc.</p>

  <p>FruitQualities() { // Default constructor</p>

  <p>// do something meaningful...</p>

  <p>}</p>

  <p>// Other constructors:</p>

  <p>// ...</p>

  <p>// Copy constructor:</p>

  <p>FruitQualities(FruitQualities f) {</p>

  <p>weight = f.weight;</p>

  <p>color = f.color;</p>

  <p>firmness = f.firmness;</p>

  <p>ripeness = f.ripeness;</p>

  <p>smell = f.smell;</p>

  <p>// etc.</p>

  <p>}</p>

  <p>}</p>

  <p>class Seed {</p>

  <p>// Members...</p>

  <p>Seed() { /* Default constructor */ }</p>

  <p>Seed(Seed s) { /* Copy constructor */ }</p>

  <p>}</p>

  <p>class Fruit {</p>

  <p>private FruitQualities fq;</p>

  <p>private int seeds;</p>

  <p>private Seed[] s;</p>

  <p>Fruit(FruitQualities q, int seedCount) {</p>

  <p>fq = q;</p>

  <p>seeds = seedCount;</p>

  <p>s = new Seed[seeds];</p>

  <p>for(int i = 0; i &lt; seeds; i++)</p>

  <p>s[i] = new Seed();</p>

  <p>}</p>

  <p>// Other constructors:</p>

  <p>// ...</p>

  <p>// Copy constructor:</p>

  <p>Fruit(Fruit f) {</p>

  <p>fq = new FruitQualities(f.fq);</p>

  <p>seeds = f.seeds;</p>

  <p>// Call all Seed copy-constructors:</p>

  <p>for(int i = 0; i &lt; seeds; i++)</p>

  <p>s[i] = new Seed(f.s[i]);</p>

  <p>// Other copy-construction activities...</p>

  <p>}</p>

  <p>// To allow derived constructors (or other</p>

  <p>// methods) to put in different qualities:</p>

  <p>protected void addQualities(FruitQualities q) {</p>

  <p>fq = q;</p>

  <p>}</p>

  <p>protected FruitQualities getQualities() {</p>

  <p>return fq;</p>

  <p>}</p>

  <p>}</p>

  <p>class Tomato extends Fruit {</p>

  <p>Tomato() {</p>

  <p>super(new FruitQualities(), 100);</p>

  <p>}</p>

  <p>Tomato(Tomato t) { // Copy-constructor</p>

  <p>super(t); // Upcast for base copy-constructor</p>

  <p>// Other copy-construction activities...</p>

  <p>}</p>

  <p>}</p>

  <p>class ZebraQualities extends FruitQualities {</p>

  <p>private int stripedness;</p>

  <p>ZebraQualities() { // Default constructor</p>

  <p>// do something meaningful...</p>

  <p>}</p>

  <p>ZebraQualities(ZebraQualities z) {</p>

  <p>super(z);</p>

  <p>stripedness = z.stripedness;</p>

  <p>}</p>

  <p>}</p>

  <p>class GreenZebra extends Tomato {</p>

  <p>GreenZebra() {</p>

  <p>addQualities(new ZebraQualities());</p>

  <p>}</p>

  <p>GreenZebra(GreenZebra g) {</p>

  <p>super(g); // Calls Tomato(Tomato)</p>

  <p>// Restore the right qualities:</p>

  <p>addQualities(new ZebraQualities());</p>

  <p>}</p>

  <p>void evaluate() {</p>

  <p>ZebraQualities zq =</p>

  <p>(ZebraQualities)getQualities();</p>

  <p>// Do something with the qualities</p>

  <p>// ...</p>

  <p>}</p>

  <p>}</p>

  <p>public class CopyConstructor {</p>

  <p>public static void ripen(Tomato t) {</p>

  <p>// Use the "copy constructor":</p>

  <p>t = new Tomato(t);</p>

  <p>System.out.println("In ripen, t is a " +</p>

  <p>t.getClass().getName());</p>

  <p>}</p>

  <p>public static void slice(Fruit f) {</p>

  <p>f = new Fruit(f); // Hmmm... will this work?</p>

  <p>System.out.println("In slice, f is a " +</p>

  <p>f.getClass().getName());</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Tomato tomato = new Tomato();</p>

  <p>ripen(tomato); // OK</p>

  <p>slice(tomato); // OOPS!</p>

  <p>GreenZebra g = new GreenZebra();</p>

  <p>ripen(g); // OOPS!</p>

  <p>slice(g); // OOPS!</p>

  <p>g.evaluate();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这个例子第一眼看上去显得有点奇怪。不同水果的质量肯定有所区别，但为什么只是把代表那些质量的数据成员直接置入Fruit（水果）类？有两方面可能的原因。第一个是我们可能想简便地插入或修改质量。注意Fruit有一个protected（受到保护的）addQualities()方法，它允许衍生类来进行这些插入或修改操作（大家或许会认为最合乎逻辑的做法是在Fruit中使用一个protected构建器，用它获取FruitQualities参数，但构建器不能继承，所以不可在第二级或级数更深的类中使用它）。通过将水果的质量置入一个独立的类，可以得到更大的灵活性，其中包括可以在特定Fruit对象的存在期间中途更改质量。</p>

  <p>之所以将FruitQualities设为一个独立的对象，另一个原因是考虑到我们有时希望添加新的质量，或者通过继承与多形性改变行为。注意对GreenZebra来说（这实际是西红柿的一类――我已栽种成功，它们简直令人难以置信），构建器会调用addQualities()，并为其传递一个ZebraQualities对象。该对象是从FruitQualities衍生出来的，所以能与基础类中的FruitQualities句柄联系在一起。当然，一旦GreenZebra使用FruitQualities，就必须将其下溯造型成为正确的类型（就象evaluate()中展示的那样），但它肯定知道类型是ZebraQualities。</p>

  <p>大家也看到有一个Seed（种子）类，Fruit（大家都知道，水果含有自己的种子）包含了一个Seed数组。</p>

  <p>最后，注意每个类都有一个副本构建器，而且每个副本构建器都必须关心为基础类和成员对象调用副本构建器的问题，从而获得“深层复制”的效果。对副本构建器的测试是在CopyConstructor类内进行的。方法ripen()需要获取一个Tomato参数，并对其执行副本构建工作，以便复制对象：</p>

  <p>t = new Tomato(t);</p>

  <p>而slice()需要获取一个更常规的Fruit对象，而且对它进行复制：</p>

  <p>f = new Fruit(f);</p>

  <p>它们都在main()中伴随不同种类的Fruit进行测试。下面是输出结果：</p>

  <p>In ripen, t is a Tomato</p>

  <p>In slice, f is a Fruit</p>

  <p>In ripen, t is a Tomato</p>

  <p>In slice, f is a Fruit</p>

  <p>从中可以看出一个问题。在slice()内部对Tomato进行了副本构建工作以后，结果便不再是一个Tomato对象，而只是一个Fruit。它已丢失了作为一个Tomato（西红柿）的所有特征。此外，如果采用一个GreenZebra，ripen()和slice()会把它分别转换成一个Tomato和一个Fruit。所以非常不幸，假如想制作对象的一个本地副本，Java中的副本构建器便不是特别适合我们。</p>

  <p>1. 为什么在C++的作用比在Java中大？</p>

  <p>副本构建器是C++的一个基本构成部分，因为它能自动产生对象的一个本地副本。但前面的例子确实证明了它不适合在Java中使用，为什么呢？在Java中，我们操控的一切东西都是句柄，而在C++中，却可以使用类似于句柄的东西，也能直接传递对象。这时便要用到C++的副本构建器：只要想获得一个对象，并按值传递它，就可以复制对象。所以它在C++里能很好地工作，但应注意这套机制在Java里是很不通的，所以不要用它。</p>

  <p>12.4 只读类</p>

  <p>尽管在一些特定的场合，由clone()产生的本地副本能够获得我们希望的结果，但程序员（方法的作者）不得不亲自禁止别名处理的副作用。假如想制作一个库，令其具有常规用途，但却不能担保它肯定能在正确的类中得以克隆，这时又该怎么办呢？更有可能的一种情况是，假如我们想让别名发挥积极的作用――禁止不必要的对象复制――但却不希望看到由此造成的副作用，那么又该如何处理呢？</p>

  <p>一个办法是创建“不变对象”，令其从属于只读类。可定义一个特殊的类，使其中没有任何方法能造成对象内部状态的改变。在这样的一个类中，别名处理是没有问题的。因为我们只能读取内部状态，所以当多处代码都读取相同的对象时，不会出现任何副作用。</p>

  <p>作为“不变对象”一个简单例子，Java的标准库包含了“封装器”（wrapper）类，可用于所有基本数据类型。大家可能已发现了这一点，如果想在一个象Vector（只采用Object句柄）这样的集合里保存一个int数值，可以将这个int封装到标准库的Integer类内部。如下所示：</p>

  <p>//: ImmutableInteger.java</p>

  <p>// The Integer class cannot be changed</p>

  <p>import java.util.*;</p>

  <p>public class ImmutableInteger {</p>

  <p>public static void main(String[] args) {</p>

  <p>Vector v = new Vector();</p>

  <p>for(int i = 0; i &lt; 10; i++)</p>

  <p>v.addElement(new Integer(i));</p>

  <p>// But how do you change the int</p>

  <p>// inside the Integer?</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>Integer类（以及基本的“封装器”类）用简单的形式实现了“不变性”：它们没有提供可以修改对象的方法。</p>

  <p>若确实需要一个容纳了基本数据类型的对象，并想对基本数据类型进行修改，就必须亲自创建它们。幸运的是，操作非常简单：</p>

  <p>//: MutableInteger.java</p>

  <p>// A changeable wrapper class</p>

  <p>import java.util.*;</p>

  <p>class IntValue {</p>

  <p>int n;</p>

  <p>IntValue(int x) { n = x; }</p>

  <p>public String toString() {</p>

  <p>return Integer.toString(n);</p>

  <p>}</p>

  <p>}</p>

  <p>public class MutableInteger {</p>

  <p>public static void main(String[] args) {</p>

  <p>Vector v = new Vector();</p>

  <p>for(int i = 0; i &lt; 10; i++)</p>

  <p>v.addElement(new IntValue(i));</p>

  <p>System.out.println(v);</p>

  <p>for(int i = 0; i &lt; v.size(); i++)</p>

  <p>((IntValue)v.elementAt(i)).n++;</p>

  <p>System.out.println(v);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>注意n在这里简化了我们的编码。</p>

  <p>若默认的初始化为零已经足够（便不需要构建器），而且不用考虑把它打印出来（便不需要toString），那么IntValue甚至还能更加简单。如下所示：</p>

  <p>class IntValue { int n; }</p>

  <p>将元素取出来，再对其进行造型，这多少显得有些笨拙，但那是Vector的问题，不是IntValue的错。</p>

  <p>12.4.1 创建只读类</p>

  <p>完全可以创建自己的只读类，下面是个简单的例子：</p>

  <p>//: Immutable1.java</p>

  <p>// Objects that cannot be modified</p>

  <p>// are immune to aliasing.</p>

  <p>public class Immutable1 {</p>

  <p>private int data;</p>

  <p>public Immutable1(int initVal) {</p>

  <p>data = initVal;</p>

  <p>}</p>

  <p>public int read() { return data; }</p>

  <p>public boolean nonzero() { return data != 0; }</p>

  <p>public Immutable1 quadruple() {</p>

  <p>return new Immutable1(data * 4);</p>

  <p>}</p>

  <p>static void f(Immutable1 i1) {</p>

  <p>Immutable1 quad = i1.quadruple();</p>

  <p>System.out.println("i1 = " + i1.read());</p>

  <p>System.out.println("quad = " + quad.read());</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Immutable1 x = new Immutable1(47);</p>

  <p>System.out.println("x = " + x.read());</p>

  <p>f(x);</p>

  <p>System.out.println("x = " + x.read());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>所有数据都设为private，可以看到没有任何public方法对数据作出修改。事实上，确实需要修改一个对象的方法是quadruple()，但它的作用是新建一个Immutable1对象，初始对象则是原封未动的。</p>

  <p>方法f()需要取得一个Immutable1对象，并对其采取不同的操作，而main()的输出显示出没有对x作任何修改。因此，x对象可别名处理许多次，不会造成任何伤害，因为根据Immutable1类的设计，它能保证对象不被改动。</p>

  <p>12.4.2 “一成不变”的弊端</p>

  <p>从表面看，不变类的建立似乎是一个好方案。但是，一旦真的需要那种新类型的一个修改的对象，就必须辛苦地进行新对象的创建工作，同时还有可能涉及更频繁的垃圾收集。对有些类来说，这个问题并不是很大。但对其他类来说（比如String类），这一方案的代价显得太高了。</p>

  <p>为解决这个问题，我们可以创建一个“同志”类，并使其能够修改。以后只要涉及大量的修改工作，就可换为使用能修改的同志类。完事以后，再切换回不可变的类。</p>

  <p>因此，上例可改成下面这个样子：</p>

  <p>//: Immutable2.java</p>

  <p>// A companion class for making changes</p>

  <p>// to immutable objects.</p>

  <p>class Mutable {</p>

  <p>private int data;</p>

  <p>public Mutable(int initVal) {</p>

  <p>data = initVal;</p>

  <p>}</p>

  <p>public Mutable add(int x) {</p>

  <p>data += x;</p>

  <p>return this;</p>

  <p>}</p>

  <p>public Mutable multiply(int x) {</p>

  <p>data *= x;</p>

  <p>return this;</p>

  <p>}</p>

  <p>public Immutable2 makeImmutable2() {</p>

  <p>return new Immutable2(data);</p>

  <p>}</p>

  <p>}</p>

  <p>public class Immutable2 {</p>

  <p>private int data;</p>

  <p>public Immutable2(int initVal) {</p>

  <p>data = initVal;</p>

  <p>}</p>

  <p>public int read() { return data; }</p>

  <p>public boolean nonzero() { return data != 0; }</p>

  <p>public Immutable2 add(int x) {</p>

  <p>return new Immutable2(data + x);</p>

  <p>}</p>

  <p>public Immutable2 multiply(int x) {</p>

  <p>return new Immutable2(data * x);</p>

  <p>}</p>

  <p>public Mutable makeMutable() {</p>

  <p>return new Mutable(data);</p>

  <p>}</p>

  <p>public static Immutable2 modify1(Immutable2 y){</p>

  <p>Immutable2 val = y.add(12);</p>

  <p>val = val.multiply(3);</p>

  <p>val = val.add(11);</p>

  <p>val = val.multiply(2);</p>

  <p>return val;</p>

  <p>}</p>

  <p>// This produces the same result:</p>

  <p>public static Immutable2 modify2(Immutable2 y){</p>

  <p>Mutable m = y.makeMutable();</p>

  <p>m.add(12).multiply(3).add(11).multiply(2);</p>

  <p>return m.makeImmutable2();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Immutable2 i2 = new Immutable2(47);</p>

  <p>Immutable2 r1 = modify1(i2);</p>

  <p>Immutable2 r2 = modify2(i2);</p>

  <p>System.out.println("i2 = " + i2.read());</p>

  <p>System.out.println("r1 = " + r1.read());</p>

  <p>System.out.println("r2 = " + r2.read());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>和往常一样，Immutable2包含的方法保留了对象不可变的特征，只要涉及修改，就创建新的对象。完成这些操作的是add()和multiply()方法。同志类叫作Mutable，它也含有add()和multiply()方法。但这些方法能够修改Mutable对象，而不是新建一个。除此以外，Mutable的一个方法可用它的数据产生一个Immutable2对象，反之亦然。</p>

  <p>两个静态方法modify1()和modify2()揭示出获得同样结果的两种不同方法。在modify1()中，所有工作都是在Immutable2类中完成的，我们可看到在进程中创建了四个新的Immutable2对象（而且每次重新分配了val，前一个对象就成为垃圾）。</p>

  <p>在方法modify2()中，可看到它的第一个行动是获取Immutable2 y，然后从中生成一个Mutable（类似于前面对clone()的调用，但这一次创建了一个不同类型的对象）。随后，用Mutable对象进行大量修改操作，同时用不着新建许多对象。最后，它切换回Immutable2。在这里，我们只创建了两个新对象（Mutable和Immutable2的结果），而不是四个。</p>

  <p>这一方法特别适合在下述场合应用：</p>

  <p>(1) 需要不可变的对象，而且</p>

  <p>(2) 经常需要进行大量修改，或者</p>

  <p>(3) 创建新的不变对象代价太高</p>

  <p>12.4.3 不变字串</p>

  <p>请观察下述代码：</p>

  <p>//: Stringer.java</p>

  <p>public class Stringer {</p>

  <p>static String upcase(String s) {</p>

  <p>return s.toUpperCase();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>String q = new String("howdy");</p>

  <p>System.out.println(q); // howdy</p>

  <p>String qq = upcase(q);</p>

  <p>System.out.println(qq); // HOWDY</p>

  <p>System.out.println(q); // howdy</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>q传递进入upcase()时，它实际是q的句柄的一个副本。该句柄连接的对象实际只在一个统一的物理位置处。句柄四处传递的时候，它的句柄会得到复制。</p>

  <p>若观察对upcase()的定义，会发现传递进入的句柄有一个名字s，而且该名字只有在upcase()执行期间才会存在。upcase()完成后，本地句柄s便会消失，而upcase()返回结果――还是原来那个字串，只是所有字符都变成了大写。当然，它返回的实际是结果的一个句柄。但它返回的句柄最终是为一个新对象的，同时原来的q并未发生变化。所有这些是如何发生的呢？</p>

  <p>1. 隐式常数</p>

  <p>若使用下述语句：</p>

  <p>String s = "asdf";</p>

  <p>String x = Stringer.upcase(s);</p>

  <p>那么真的希望upcase()方法改变自变量或者参数吗？我们通常是不愿意的，因为作为提供给方法的一种信息，自变量一般是拿给代码的读者看的，而不是让他们修改。这是一个相当重要的保证，因为它使代码更易编写和理解。</p>

  <p>为了在C++中实现这一保证，需要一个特殊关键字的帮助：const。利用这个关键字，程序员可以保证一个句柄（C++叫“指针”或者“引用”）不会被用来修改原始的对象。但这样一来，C++程序员需要用心记住在所有地方都使用const。这显然易使人混淆，也不容易记住。</p>

  <p>2. 覆盖"+"和StringBuffer</p>

  <p>利用前面提到的技术，String类的对象被设计成“不可变”。若查阅联机文档中关于String类的内容（本章稍后还要总结它），就会发现类中能够修改String的每个方法实际都创建和返回了一个崭新的String对象，新对象里包含了修改过的信息――原来的String是原封未动的。因此，Java里没有与C++的const对应的特性可用来让编译器支持对象的不可变能力。若想获得这一能力，可以自行设置，就象String那样。</p>

  <p>由于String对象是不可变的，所以能够根据情况对一个特定的String进行多次别名处理。因为它是只读的，所以一个句柄不可能会改变一些会影响其他句柄的东西。因此，只读对象可以很好地解决别名问题。</p>

  <p>通过修改产生对象的一个崭新版本，似乎可以解决修改对象时的所有问题，就象String那样。但对某些操作来讲，这种方法的效率并不高。一个典型的例子便是为String对象覆盖的运算符“+”。“覆盖”意味着在与一个特定的类使用时，它的含义已发生了变化（用于String的“+”和“+=”是Java中能被覆盖的唯一运算符，Java不允许程序员覆盖其他任何运算符――注释④）。</p>

  <p>④：C++允许程序员随意覆盖运算符。由于这通常是一个复杂的过程（参见《Thinking in C++》，Prentice-Hall于1995年出版），所以Java的设计者认定它是一种“糟糕”的特性，决定不在Java中采用。但具有讽剌意味的是，运算符的覆盖在Java中要比在C++中容易得多。</p>

  <p>针对String对象使用时，“+”允许我们将不同的字串连接起来：</p>

  <p>String s = "abc" + foo + "def" + Integer.toString(47);</p>

  <p>可以想象出它“可能”是如何工作的：字串"abc"可以有一个方法append()，它新建了一个字串，其中包含"abc"以及foo的内容；这个新字串然后再创建另一个新字串，在其中添加"def"；以此类推。</p>

  <p>这一设想是行得通的，但它要求创建大量字串对象。尽管最终的目的只是获得包含了所有内容的一个新字串，但中间却要用到大量字串对象，而且要不断地进行垃圾收集。我怀疑Java的设计者是否先试过种方法（这是软件开发的一个教训――除非自己试试代码，并让某些东西运行起来，否则不可能真正了解系统）。我还怀疑他们是否早就发现这样做获得的性能是不能接受的。</p>

  <p>解决的方法是象前面介绍的那样制作一个可变的同志类。对字串来说，这个同志类叫作StringBuffer，编译器可以自动创建一个StringBuffer，以便计算特定的表达式，特别是面向String对象应用覆盖过的运算符+和+=时。下面这个例子可以解决这个问题：</p>

  <p>//: ImmutableStrings.java</p>

  <p>// Demonstrating StringBuffer</p>

  <p>public class ImmutableStrings {</p>

  <p>public static void main(String[] args) {</p>

  <p>String foo = "foo";</p>

  <p>String s = "abc" + foo +</p>

  <p>"def" + Integer.toString(47);</p>

  <p>System.out.println(s);</p>

  <p>// The "equivalent" using StringBuffer:</p>

  <p>StringBuffer sb =</p>

  <p>new StringBuffer("abc"); // Creates String!</p>

  <p>sb.append(foo);</p>

  <p>sb.append("def"); // Creates String!</p>

  <p>sb.append(Integer.toString(47));</p>

  <p>System.out.println(sb);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>创建字串s时，编译器做的工作大致等价于后面使用sb的代码――创建一个StringBuffer，并用append()将新字符直接加入StringBuffer对象（而不是每次都产生新对象）。尽管这样做更有效，但不值得每次都创建象"abc"和"def"这样的引号字串，编译器会把它们都转换成String对象。所以尽管StringBuffer提供了更高的效率，但会产生比我们希望的多得多的对象。</p>

  <p>12.4.4 String和StringBuffer类</p>

  <p>这里总结一下同时适用于String和StringBuffer的方法，以便对它们相互间的沟通方式有一个印象。这些表格并未把每个单独的方法都包括进去，而是包含了与本次讨论有重要关系的方法。那些已被覆盖的方法用单独一行总结。</p>

  <p>首先总结String类的各种方法：</p>

  <p>方法 自变量，覆盖 用途</p>

  <p>构建器 已被覆盖：默认，String，StringBuffer，char数组，byte数组 创建String对象</p>

  <p>length() 无 String中的字符数量</p>

  <p>charAt() int Index 位于String内某个位置的char</p>

  <p>getChars()，getBytes 开始复制的起点和终点，要向其中复制内容的数组，对目标数组的一个索引 将char或byte复制到外部数组内部</p>

  <p>toCharArray() 无 产生一个char[]，其中包含了String内部的字符</p>

  <p>equals()，equalsIgnoreCase() 用于对比的一个String 对两个字串的内容进行等价性检查</p>

  <p>compareTo() 用于对比的一个String 结果为负、零或正，具体取决于String和自变量的字典顺序。注意大写和小写不是相等的！</p>

  <p>regionMatches() 这个String以及其他String的位置偏移，以及要比较的区域长度。覆盖加入了“忽略大小写”的特性 一个布尔结果，指出要对比的区域是否相同</p>

  <p>startsWith() 可能以它开头的String。覆盖在自变量里加入了偏移 一个布尔结果，指出String是否以那个自变量开头</p>

  <p>endsWith() 可能是这个String后缀的一个String 一个布尔结果，指出自变量是不是一个后缀</p>

  <p>indexOf(),lastIndexOf() 已覆盖：char，char和起始索引，String，String和起始索引 若自变量未在这个String里找到，则返回-1；否则返回自变量开始处的位置索引。lastIndexOf()可从终点开始回溯搜索</p>

  <p>substring() 已覆盖：起始索引，起始索引和结束索引 返回一个新的String对象，其中包含了指定的字符子集</p>

  <p>concat() 想连结的String 返回一个新String对象，其中包含了原始String的字符，并在后面加上由自变量提供的字符</p>

  <p>relpace() 要查找的老字符，要用它替换的新字符 返回一个新String对象，其中已完成了替换工作。若没有找到相符的搜索项，就沿用老字串</p>

  <p>toLowerCase(),toUpperCase() 无 返回一个新String对象，其中所有字符的大小写形式都进行了统一。若不必修改，则沿用老字串</p>

  <p>trim() 无 返回一个新的String对象，头尾空白均已删除。若毋需改动，则沿用老字串</p>

  <p>valueOf() 已覆盖：object，char[]，char[]和偏移以及计数，boolean，char，int，long，float，double 返回一个String，其中包含自变量的一个字符表现形式</p>

  <p>Intern() 无 为每个独一无二的字符顺序都产生一个（而且只有一个）String句柄</p>

  <p>可以看到，一旦有必要改变原来的内容，每个String方法都小心地返回了一个新的String对象。另外要注意的一个问题是，若内容不需要改变，则方法只返回指向原来那个String的一个句柄。这样做可以节省存储空间和系统开销。</p>

  <p>下面列出有关StringBuffer（字串缓冲）类的方法：</p>

  <p>方法 自变量，覆盖 用途</p>

  <p>构建器 已覆盖：默认，要创建的缓冲区长度，要根据它创建的String 新建一个StringBuffer对象</p>

  <p>toString() 无 根据这个StringBuffer创建一个String</p>

  <p>length() 无 StringBuffer中的字符数量</p>

  <p>capacity() 无 返回目前分配的空间大小</p>

  <p>ensureCapacity() 用于表示希望容量的一个整数 使StringBuffer容纳至少希望的空间大小</p>

  <p>setLength() 用于指示缓冲区内字串新长度的一个整数 缩短或扩充前一个字符串。如果是扩充，则用null值填充空隙</p>

  <p>charAt() 表示目标元素所在位置的一个整数 返回位于缓冲区指定位置处的char</p>

  <p>setCharAt() 代表目标元素位置的一个整数以及元素的一个新char值 修改指定位置处的值</p>

  <p>getChars() 复制的起点和终点，要在其中复制的数组以及目标数组的一个索引 将char复制到一个外部数组。和String不同，这里没有getBytes()可供使用</p>

  <p>append() 已覆盖：Object，String，char[]，特定偏移和长度的char[]，boolean，char，int，long，float，double 将自变量转换成一个字串，并将其追加到当前缓冲区的末尾。若有必要，同时增大缓冲区的长度</p>

  <p>insert() 已覆盖，第一个自变量代表开始插入的位置：Object，String，char[]，boolean，char，int，long，float，double 第二个自变量转换成一个字串，并插入当前缓冲区。插入位置在偏移区域的起点处。若有必要，同时会增大缓冲区的长度</p>

  <p>reverse() 无 反转缓冲内的字符顺序</p>

  <p>最常用的一个方法是append()。在计算包含了+和+=运算符的String表达式时，编译器便会用到这个方法。insert()方法采用类似的形式。这两个方法都能对缓冲区进行重要的操作，不需要另建新对象。</p>

  <p>12.4.5 字串的特殊性</p>

  <p>现在，大家已知道String类并非仅仅是Java提供的另一个类。String里含有大量特殊的类。通过编译器和特殊的覆盖或过载运算符+和+=，可将引号字符串转换成一个String。在本章中，大家已见识了剩下的一种特殊情况：用同志StringBuffer精心构造的“不可变”能力，以及编译器中出现的一些有趣现象。</p>

  <p>12.5 总结</p>

  <p>由于Java中的所有东西都是句柄，而且由于每个对象都是在内存堆中创建的――只有不再需要的时候，才会当作垃圾收集掉，所以对象的操作方式发生了变化，特别是在传递和返回对象的时候。举个例子来说，在C和C++中，如果想在一个方法里初始化一些存储空间，可能需要请求用户将那片存储区域的地址传递进入方法。否则就必须考虑由谁负责清除那片区域。因此，这些方法的接口和对它们的理解就显得要复杂一些。但在Java中，根本不必关心由谁负责清除，也不必关心在需要一个对象的时候它是否仍然存在。因为系统会为我</p>

  <div class="mbppagebreak"></div>
</body>
</html>
